package org.htu.tinyspaces.impl;

import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;

import org.htu.tinyspaces.api.Entry;
import org.htu.tinyspaces.api.TinySpace;
import org.htu.tinyspaces.error.TinyException;

/**
 * TinySpaces implementation class. Where the magic happens.
 * @author rich.midwinter
 *
 */
public final class TinySpaceImpl extends UnicastRemoteObject implements
        TinySpace {
    /**
     * For serialisation.
     */
    private static final long serialVersionUID = -5470175427706178320L;

    /**
     * Current underlying space model.
     */
    private List<Entry> s = new ArrayList<Entry>();

    /**
     * Constructor for our space.
     * @throws RemoteException For any remote exceptions.
     */
    public TinySpaceImpl() throws RemoteException {
    }

    /**
     * @param o The Entry to write.
     * @throws RemoteException For any remote exceptions.
     * @throws TinyException For any TinySpace specific exceptions.
     */
    public void write(final Entry o) throws RemoteException,
            TinyException {
        s.add(o);
    }

    /**
     * Reads an Entry from the space.
     * @param o The Entry description object.
     * @return The Entry from the space.
     * @throws RemoteException For any remote exceptions.
     * @throws TinyException For any TinySpace specific exceptions.
     */
    public Entry read(final Entry o, final Long timeout) throws RemoteException,
            TinyException {
        final Long endTime = System.currentTimeMillis() +timeout;
        
        Entry entry;
        
        try {
            while ((entry = readIfExists(o)) != null) {
                Thread.sleep(100);
                
                if (System.currentTimeMillis() > endTime) {
                    break;
                }
            }
        } catch (Exception e) {
            throw new TinyException("TinySpace.read() interrupted: "
                    + e.getMessage(), e);
        }
        
        return entry;
    }

    /**
     * Reads an Entry if it exists.
     * @param o The Entry to read.
     */
    public Entry readIfExists(final Entry o)
            throws RemoteException, TinyException {
        if (o == null) return null;
        
        try {
            final Field[] fieldList = o.getClass().getFields();
            for (Entry e : s) {
                boolean match = true;
                for (Field fld : fieldList) {
                    if (fld.get(o) != null) {
                        if (!fld.get(o).equals(fld.get(e))) {
                            match = false;
                            break;
                        }
                    }
                }
                if (match) {
                    return e;
                }
            }
        } catch (Exception e) {
            throw new TinyException("TinySpace.read() failed: "
                    + e.getMessage(), e);
        }
        return null;
    }

    /**
     * Reads and removes an Entry from the space.
     * @param o The Entry description.
     * @return The Entry.
     * @throws RemoteException For any remote exceptions.
     * @throws TinyException For any TinySpace specific exceptions.
     */
    public Entry take(final Entry o, final Long timeout)
            throws RemoteException, TinyException {
        final Entry e = read(o, timeout);
        if (e != null) {
            s.remove(e);
        }
        return e;
    }

    /**
     * Takes an Entry if it exists.
     * @param o The entry object to retrieve.
     */
    public Entry takeIfExists(final Entry o) throws RemoteException,
            TinyException {
        final Entry e = readIfExists(o);
        if (e != null) {
            s.remove(e);
        }
        return e;
    }
}
